﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using InnovTech.Persistence;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace InnovTech.Persistence
{
    public abstract class NHVirtualEnvironmentBase<TDatabaseInfo> :
        IVirtualDatabaseEnvironment<TDatabaseInfo>, ISqlGenerator
        where TDatabaseInfo : INHDatabaseInfo
    {
        NHDatabaseAccess<TDatabaseInfo> _dataAccess;
        NHDatabaseGeneratorBase<TDatabaseInfo> _databaseGenerator;
        protected ISessionFactoryBuilder<TDatabaseInfo> _sessionFactoryBuilder;

        protected NHVirtualEnvironmentBase() { }

        public NHVirtualEnvironmentBase(ISessionFactoryBuilder<TDatabaseInfo> sessionFactoryBuilder)
        {
            _sessionFactoryBuilder = sessionFactoryBuilder;
        }

        ISession CurrentSession { get; set; }

        /// <summary>
        /// Gets the current used IDatabaseInfo
        /// </summary>
        public TDatabaseInfo DatabaseInfo
        {
            get;
            protected set;
        }

        NHDatabaseAccess<TDatabaseInfo> DataAccess
        {
            get
            {
                if (_dataAccess == null)
                    _dataAccess = new NHDatabaseAccess<TDatabaseInfo>(_sessionFactoryBuilder);
                return _dataAccess;
            }
        }

        NHDatabaseGeneratorBase<TDatabaseInfo> DatabaseGenerator
        {
            get
            {
                if (_databaseGenerator == null)
                    _databaseGenerator = CreateDatabaseGenerator(_sessionFactoryBuilder);
                return _databaseGenerator;
            }
        }

        

        /// <summary>
        /// Indicates whether the current database environment is running.
        /// </summary>
        public bool IsRunning
        {

            get;
            set;
        }


        /// <summary>
        /// Starts the database environment
        /// </summary>
        /// <param name="databaseInfo"></param>
        public IDatabaseAccess<TDatabaseInfo> Start()
        {
            if (IsRunning) throw new Exception("The virtual database environment is already running");
            DatabaseInfo = DatabaseGenerator.GenerateDatabase();
            IsRunning = true;
            return DataAccess;
        }
        /// <summary>
        /// Stops the running database environment.
        /// </summary>
        public void Stop()
        {
            if (!IsRunning) throw new Exception("The virtual database environment is not running");
            MultiUserSingleConnectionProvider.ExplicitlyDestroyConnection();
            CloseCurrentSession();
            Cleanup(DatabaseInfo);
            DatabaseInfo = default(TDatabaseInfo);
            IsRunning = false;
        }

        public void DoSessionUnitWork(Action<IDao> action)
        {
            DataAccess.DoSessionUnitWork<object>(dao =>
            {
                action(dao);
                return null;
            });
        }

        private void CloseCurrentSession()
        {
            if (CurrentSession != null && CurrentSession.IsOpen)
            {
                CurrentSession.Close();
                CurrentSession = null;
            }
        }

        public void DoTransactionUnitWork(Action<IDao> action, bool shouldCloseSession = false)
        {
            if (CurrentSession == null || !CurrentSession.IsOpen)
            {
                CurrentSession = DataAccess.CreateSession();
            }
            DataAccess.DoTransactionUnitWork<object>(CurrentSession, dao =>
            {
                action(dao);
                return null;
            });
            if (shouldCloseSession)
                CurrentSession.Close();
        }

        /// <summary>
        /// Generates the database-schema as sql script.
        /// </summary>
        /// <returns></returns>
        public string GenerateSqlScript()
        {
            SchemaExport exporter = new SchemaExport(_sessionFactoryBuilder.BuildConfiguration());
            string tmpPath = Directory.GetCurrentDirectory() + "\\" + Guid.NewGuid().ToString() + ".sql";
            exporter.SetOutputFile(tmpPath);
            exporter.Create(true, false);

            string result = null;
            if (File.Exists(tmpPath))
            {
                using (FileStream stream = File.OpenRead(tmpPath))
                using (StreamReader reader = new StreamReader(stream))
                {
                    result = reader.ReadToEnd();
                    reader.Close();
                    stream.Close();
                }

                File.Delete(tmpPath);
            }
            return result;
        }

        /// <summary>
        /// Cleans up after closing the database environment.
        /// </summary>
        /// <param name="databaseInfo"></param>
        protected abstract void Cleanup(TDatabaseInfo databaseInfo);
        
        /// <summary>
        /// Creates a <see cref="NHDatabaseGeneratorBase"/>.
        /// </summary>
        /// <param name="_sessionFactoryBuilder"></param>
        /// <returns></returns>
        protected abstract NHDatabaseGeneratorBase<TDatabaseInfo> CreateDatabaseGenerator(ISessionFactoryBuilder<TDatabaseInfo> _sessionFactoryBuilder);
    }
}
